Tutoriels/Apprendre à programmer en Lua/Les fonctions
Les fonctions sont un des éléments fondamentaux des langages de programmation. Mais c'est aussi une notion qui n'est pas forcément facile à comprendre pour des débutants, n'hésitez donc pas à relire le chapitre autant de fois que nécessaire. Je vais commencer par faire deux analogies qui peuvent vous aider à comprendre ce qu'est une fonction : Une fonction c'est un peu comme un recette de cuisine : il y a des ingrédients au début, sur lesquels on travaille en les transformant afin de produire quelque chose à la fin. Une deuxième bonne analogie est celle de l'usine : tout comme une fonction, une usine créé un produit finis (ce qui sort de l'usine, qui y a été construit) à partir de matières premières (ce qui y rentre, parfois dans un état très différent de celui dans lequel il sera utilisé). L'usine transforme et assemble les matières premières afin de construire le produit finis. Sinon, en terme informatique voici comment l'on peut décrire une fonction : *Une fonction est un segment de code qui porte un nom et peut être exécuté à volonté et sur demande lorsqu'elle est appelée par son nom. *Le code qui appelle une fonction peut optionnellement lui transmettre des données lors de l'appel. Une fonction peut donc recevoir des données lors de son appel via ses arguments. *Une fonction peut optionnellement retourner des valeurs, c'est à dire que le code ayant appelée une fonction peut recevoir ces valeurs en retour. D'une manière générale, les fonctions servent à réutiliser un segment de code en modifiant éventuellement son comportement grâce aux données d'entrée -les arguments- et éventuellement à récupérer le fruit de son travail via les valeurs qu'elle peut retourner. Un segment de code qui porte un nom Déclarer (créer) une fonction utilise le mot clé function ainsi que deux parenthèses : function nom de la fonction() de la fonction end Rappelez-vous qu'en Lua, les fonctions ne sont que des variables comme les autres, mais de type "function". On peut donc également déclarer une fonction de la manière suivante (exactement comme lorsque l'on donne une valeur à une variable) : nom de la fonction = function() de la fonction end Néanmoins, j'utiliserais toujours la première notation dans les exemples de ce chapitre. Copiez/collez la fonction suivante dans votre script, nous allons l'utiliser et la modifier tout au long du chapitre : function Multiplier() nombre = 5 facteur = 2 resultat = nombre * facteur print( resultat ) end -- cette fonction multiplie simplement un nombre par un facteur et affiche le résultat Appeler une fonction Pour appeler une fonction, il suffit d'écrire son nom suivit des parenthèses : Multiplier() -- cela vous affiche : -- 10 Comme dit dans l'introduction, le code qui est contenu dans la fonction "Multiplier" est exécuté lorsque celle-ci est appelée. C'est comme si le programme remplaçait dans votre script "Multiplier()" par le code qu'il trouve dans la fonction. Vous voyez ici l'un des avantages à utiliser des fonctions : en un seul appel à notre fonction (qui fait 12 caractères), nous avons fait appel au travail nécessitant normalement 80 caractères. C'est en quelque sorte un raccourcit qui vous permet de remplacer tout un segment de code par un seul mot (le nom de la fonction). L’intérêt peut ici paraitre limité puisque notre fonction ne fait pas encore beaucoup de choses mais lorsqu'elles font plusieurs dizaines voire centaines de lignes, on est content lorsque l'on n'a pas à tout réécrire plusieurs fois. Les arguments La vraie magie des fonctions est que le code qu'elle contient n'est pas statique. On peut transmettre des variables aux fonctions lorsqu'elles sont appelées et ainsi faire varier leur comportement ou bien rendre le même code utilisable dans différentes occasions. Prenez notre fonction Multiplier : actuellement elle ne sert vraiment à rien puisqu'elle ne fait que multiplier 5 par 2. Voyons plutôt comment l'utiliser pour multiplier n'importe quel nombre par n'importe quel facteur. Manipuler les arguments n'est pas difficile : il suffit de les écrire entre les parenthèses en les séparant par des virgules si il y en a plusieurs, tant lors de l'appel que lors de la définition de la fonction. Exemple : -- appel de la fonction avec deux arguments (dont les valeurs sont "un" et "deux") maVariable = "deux" MaFonction("un", maVariable) -- définition function MaFonction(argument1, argument2) -- 'argument1' est une variable (dont vous choisissez le nom) qui existe dans la fonction uniquement et qui prend la première valeur passée à la fonction lors de l'appel (ici, c'est "un") -- 'argument2' prend la deuxième valeur passée à la fonction lors de l'appel ("deux") end Le nombre d'argument n'est pas limité. Si vous passez lors de l'appel moins d'arguments que la fonction n'est capable d'en recevoir, ceux pour lesquels aucune valeur n'aura été passée vaudront simplement nil dans la fonction. A l'inverse, si vous passez lors de l'appel plus d'arguments que la fonction n'est capable d'en recevoir, les arguments en trop seront simplement ignorés. Exemple : MaFonction("un") function MaFonction(argument1, argument2) -- argument1 vaut "un" -- argument2 vaut nil end -- ou à l'inverse : MaFonction("un", "deux", "trois") function MaFonction(argument1, argument2) -- argument1 vaut "un" -- argument2 vaut "deux" -- la valeur "trois" est juste ignorée end Mettons ça en pratique avec notre fonction Mutiplier : fonction Multiplier(nombre, facteur) resultat = nombre * facteur print(resultat) end Multiplier(4, 3) -- affiche 12 Multiplier(8, 8) -- affiche 64 Et c'est tout. Grâce aux arguments, nous avons transformé une fonction statique et inutile en quelque chose d'un peu plus flexible (bien que dans cet exemple, l'utilité de la fonction reste toujours limitée). Nombre variable d'argument Il est possible de dire à une fonction qu'elle est susceptible de recevoir un nombre variable d'arguments. Il suffit d'écrire à la place du nom des variables trois point les uns à la suite des autres : function MaFonction(...) -- end -- ou bien function MaFonction(argument1, argument2, ...) -- les trois points peuvent être précédés d'autant d'arguments que vous le souhaitez end Dans ce cas, il existe automatiquement une variable arg à l'intérieur de la fonction qui est une table contenant les arguments, quelque soit leur nombre. Démonstration avec cette fonction : function AfficheArguments(...) -- rappelez vous du chapitre sur les boucles, -- ici on itère simplement sur la variable arg -- et affiche les valeurs qu'elle contient -- donc les les arguments que la fonction a reçut for i, argument in ipairs(arg) do print(argument) end end AfficheArguments("un", 2) -- affiche : -- un -- 2 AfficheArguments(1, "deux", true, {}) -- affiche : -- 1 -- deux -- true -- table: 05FD7548 (ou quelque chose comme ça) Retourner des valeurs Retourner une valeur permet de récupérer le fruit du travail de la fonction. La fonction donne la valeur au code qui l'a appelée afin que celui-ci puisse continuer son cheminement avec l'information dont il avait besoin. Pour cela cela il suffit d'utiliser dans la fonction le mot clé return suivit de la valeur (ou de la variable) à retourner. Exemple : fonction Multiplier(nombre, facteur) resultat = nombre * facteur return resultat -- "print(resultat)" a été remplacé par "return resultat" end j Multiplier(5, 3) -- n'affiche rien puisque la fonction ne fait plus que retourner le résultat, au lieu de l'afficher comme avant resultat = Multiplier(5, 3) -- ici la valeur retournée par la fonction est mise dans la variable resultat print(resultat) -- affiche 15 Stopper la fonction Le mot clé return a aussi pour effet d'arrêter l'exécution de la fonction quelque soit son emplacement dans le code de celle-ci (en plus d'éventuellement retourner la ou les valeurs qui le suivent). Un petit exemple pour vous démontrer l'effet du sur l'exécution de la fonction : function Compte() for i=1, 10 do print(i) end print("J'ai terminé de compter.") end Compte() -- Comme dans le chapitre sur les boucles, cela affiche : -- 1 -- ... -- 10 -- J'ai terminé de compter Maintenant arrêtons simplement la boucle lorsque i est égal à 5 : function Compte() for i=1, 10 do if i 5 then break end print(i) end print("J'ai terminé de compter.") end Compte() -- cela affiche : -- 1 -- ... -- 4 -- j'ai terminé de compter Lorsque le mot clé break est lu, cela arrête seulement la boucle et permet à la fonction de poursuivre et terminer son exécution. Maintenant enfin, utilisons le mot clé return à la place de break : function Compte() for i=1, 10 do if i 5 then return end print(i) end print("J'ai terminé de compter.") end Compte() -- cela affiche : -- 1 -- ... -- 4 Comme vous le constatez, cela n'affiche pas "J'ai terminé de compter" car la fonction s’arrête complètement sans poursuivre son exécution dès qu'elle lit le mot clé return. Retourner plusieurs valeurs C'est assez rare pour le noter, les fonctions en Lua ont également la capacité de retourner plusieurs valeurs à la fois. Il suffit de les séparer par des virgules après le mot clé return. Exemple : function MaFonction() deux = "deux" return "un", deux end variable1, variable2 = MaFonction() -- souvenez-vous que c'est ainsi que l'on déclare plusieurs variables en une seule ligne -- variable1 vaut "un", variable2 vaut "deux" Les objets Nous avons vu dans le chapitre sur les tableaux qu'ils peuvent contenir n'importe quelle type de donnée, ils peuvent donc contenir des fonctions. Ce que l'on appel un objet est typiquement un tableau qui contient (entre autre) des fonctions. Placer des fonctions dans un objet est autant une question d'organisation du code que de contrôle de la manière dont les fonctions sont appelées. Ainsi dans CraftStudio, chaque objet de jeu dans vos scènes est représenté par un objet (un tableau en Lua) qui contient les informations propres à cet objet de jeu en particulier et sur lequel vous pouvez appeler toute une série de fonctions utiles pour leur manipulation. De la même manière, l'objet "CraftStudio" contient des fonctions d'utilité générique ainsi que d'autre objets plus spécialisés comme l'objet "CraftStudio.Input" qui contient les fonctions relatives au contrôles (clavier/souris) de votre jeu. -- Déclaration d'une fonction dans un objet (dans un tableau) Objet = { -- dans le constructeur de l'objet Fonction = function() print("une fonction") end } -- ou encore Objet.Fonction = function() print("une fonction") end -- ou encore Objet"Fonction" = function() print("une fonction") end -- ou bien (le plus courant) function Objet.Fonction() print("une fonction") end -- Appel de la fonction : Objet"Fonction"() -- ou bien (le plus courant) Objet.Fonction() -- affiche "une fonction" Aucun de ces exemples ne devraient vous interloquer, puisqu'ils ont tous été vus dans le chapitre sur les tables. Il s'agit juste ici de crée une entrée dans le tableau avec une fonction pour valeur. Ce qu'il y a à remarquer, c'est que dans trois des exemples (dont les deux indiqués comme étant les plus courants), le nom de l'objet et de la fonction sont séparés par un point, exactement comme avec une variable contenue dans un tableau. Or concernant uniquement les fonctions, vous verrez très fréquemment une syntaxe équivalente, mais avec un double point, au lieu d'un simple point : -- déclaration : function Objet:Fonction() -- end -- notez que la syntaxe ci-dessous n'est PAS autorisée, seule celle ci-dessus l'est Objet:Fonction = function() -- seule l'utilisation d'un simple point est autorisée avec cette syntaxe end -- appel : Objet:Fonction() La différence entre les deux syntaxes n'est pas énorme mais il convient de bien la comprendre. La syntaxe avec le double point est ce que l'on appel un sucre syntaxique (un raccourci) pour la syntaxe avec un seul point. -- lors de la déclaration : function Objet:Fonction(arg1, arg2) end -- est équivalent à écrire function Objet.Fonction(self, arg1, arg2) end -- lors de l'appel Objet:Fonction("arg1", "arg2") -- est équivalent à écrire Objet.Fonction(Objet, "arg1", "arg2") -- l'objet est explicitement passé comme premier argument En d'autre thermes, déclarer une fonction avec le double point fait exister automatiquement la variable self dans la fonction. Dans ce cas, la valeur de self sera toujours celle du premier argument (qui dépend de la syntaxe utilisée lors de l'appel !). Lorsque la fonction est appelée avec le double point, le premier argument n'est en fait pas le premier argument après la parenthèse ouvrante (c'est à dire "arg1"), mais est l'objet sur lequel la fonction est appelée (c'est à dire "Objet" dans notre exemple). -- exemple 1 : déclaration avec le simple point function Objet.Fonction(arg1, arg2, arg3) -- la fonction est déclarée avec un simple point, self n'existe pas automatiquement print(self, arg1, arg2, arg3) end Objet:Fonction("arg1", "arg2") -- affiche quelque chose comme "nil table: 04FB5C48 arg1 arg2" car self n'existe pas mais le premier argument est l'objet. Objet.Fonction("arg1", "arg2") -- affiche quelque chose comme "nil arg1 arg2 nil" car self n'existe pas et l'objet n'est pas le premier argument. -- exemple 2 : cas inverse : déclaration avec le double point function Objet:Fonction(arg1, arg2, arg3) -- self existera et prendra la valeur du premier argument, que ce soit l'objet ou n'importe quelle autre valeur print(self, arg1, arg2, arg3) end Objet:Fonction("arg1", "arg2") -- affichera quelque chose comme "table: 050A5C20 arg1 arg2 nil" car self existe et prend la valeur du premier argument qui se trouve être l'objet Objet.Fonction("arg1", "arg2") -- affichera quelque chose comme "arg1 arg2 nil nil" car self existe et prend la valeur du premier argument qui n'est pas l'objet Pour résumer : Définition avec deux points : la variable "self" existe dans la fonction et prend la valeur de son premier argument. Appel avec deux points : L'objet est implicitement passé en tant que premier argument à la fonction. Conclusion Autant que les variables, les conditions, les tableaux et les boucles, les fonctions font partie des structures qui confèrent au langages de programmation (et donc à nous, programmeur) la capacité de créer un programme interactif fonctionnant sur un ordinateur. Suivant les langages, il existe encore bien d'autres éléments. C'est également le cas en Lua, mais vous avez d'ors et déjà les bases pour commencer à créer un jeu complet avec les seules informations extraites de ces tutoriels. Le prochain chapitre présentera la manière dont les scripts sont utilisés dans CraftStudio.